package com.hsc.box;

/**
 * 从一个用户的需求去思考：
 * 数组作为一个小的容器去存储数据的话，他的长度是固定的，而且用起来也麻烦
 * 能否设计一个容器，能做一些类似数组存取的事情呢？更加方便我们的使用呢？
 * 1、数组的长度不可变，
 * 2、而且删除添加数据也麻烦，能否简化这个事情呢？
 * 3、总结起来就是，我只管用，其他底层的细节不管
 * 4、从而可以推导出来ArrayList的产生，
 * <p>
 * 做完我们发现（ArrayList推导）：
 * ArrayBox特点：
 * 适合存储数据长度是可变的
 * 适合遍历，底层其实就是一个动态的数组，数组底层的内存地址是连续的
 * 不适合添加和删除元素，因为需要将原来的数组的中的数据挨个循环移动元素，性能会很慢
 * 上面的封装ArrayBox做的事情是不是就是我们经常使用到的集合ArrayList呢，
 * ArrayList的底层的实现也是类似的，包括其功能特性
 * <p>
 * 思考（LinkedList推导）：
 * 由于底层使用的是数组这个数据结构从而决定了这个性能的特性，或者说性能的瓶颈所在，能否有新的容器能够很好的解决这个问题呢？
 * 如果我们寻求新的容器进行存储，不再使用数组来进行存在，我们能否用对象来进行存储元素呢？而且对象的创建是存放在堆内存空间的，对象中声明的属性是否也就可以进程元素的存储
 * 将每次要存储的元素创建对象来存储，用面向对象的思想进行管理，这样删除和添加数据的时候，只要操作内存的地址是不是就可以了呢，从而提升效率，每个对象作为一个节点对象来进行存储，
 * 这样的结构是不是就很像我们经常使用的LinkedList，LinkedList底层就是使用的链表进行存储的。
 *
 * 思考:可扩展性（面向对象思想）思考：代码的可扩展性，健壮性
 * 实现类中继承了一个抽象类，并且实现了接口中定义的主要方法，做了一个良好的规范
 */
public class ArrayBox<T> extends AbstractBox<T>{


    //默认大小的容量的数值
    private static final int DEFAULT_CAPACITY = 10;
    //这里我们使用的数组来装数据，数组的特点是长度一旦确定就不能再次发生改变
    private Object[] elementData;
    //创建一个计数器。来记录容器的有效元素个数
    private int size = 0;

    /**
     * 创建一个构造方法，当我们的ArrayBox对象创建时，就准备好一个数组，用来装数据
     */
    public ArrayBox() {
        //创建对象默认长度大小的数组,默认创建的长度为10的数组
        //在这里我们可以思考一下，我们为何不直接写数字10呢？从代码的可读性去思考，如果我们代码是给别人使用的一个工具，是否有一个见明知意的效果呢？
        elementData = new Object[DEFAULT_CAPACITY];
    }

    /**
     * 重载构造方法，提供一个方法给使用者自己去设置容器的大小，初始化容量
     *
     * @param initialCapacity 初始化容量大小
     */
    public ArrayBox(int initialCapacity) {
        elementData = new Object[initialCapacity];
    }


    /**
     * 设计一个方法，我们需要提供个什么样的返回值来进行结果的确认？
     * 在面向对象的思想角度去，或者从执行的效率上，我们可以返回一个整型的类型也可以返回一个布尔类型的返回值，
     * 对于用户来说，我知需要得到一个添加成功的返回值就可以了,所以我们这里选择返回的是一个布尔值
     *
     * @param element 泛型的支持：返回值直接使用用户传入的数据类型，
     */
    public boolean add(T element) {
        //1、当用户添加一个数据进来的时候，
        // 第一：首先先对元素进行一个存储，第二：需要判断，确保我们的容器能否保存得下来
        //所以此时我们需要设计一个方法确认当前的内部的数组容量能否装得下
        //我们添加一个元素就说明实际的元素的个数就会加1，说明我们的容器至少需要的最小的容量是size+1才能存入数据
        this.ensureCapacityInternal(size + 1);
        //插入元素，我们从数组的后进行元素的添加，因为数组的索引是从0开始的，所以我们添加元素的下标刚好是当前实际元素数量+1,
        // 比如：当前数组的实际个数size为5,我们插入的第6个元素的时候，第六个元素的数组的索引下标就是5了，所以可以直接拿size当做数组索引下标
        //这里解释一下size++的含义：
        // 当size++放在组合的计算时，先对size先赋值，后自增
        //所以elementData[size++]
        // 实际分为了两步的计算：1、elementData[size[size]= element：先根据数组的下标插入元素 2、size++ ：赋值完后再对size本身加1，实际的数量加1
        elementData[size++] = element;
        //添加成功，返回
        return true;
    }

    /**
     * 设计一个方法的重载，指定位置添加指定的元素
     *
     * @param index
     * @param element
     */
    public boolean add(int index, T element) {
        //确保当前的数组的容量够用
        this.ensureCapacityInternal(size + 1);
        //上面代码没有报异常，说明当前的数组的容量够用
        //添加元素思路：将添加元素的index位置空出来，然后从size---index,挨个往后移动覆盖
        for (int i = size - 1; i >= index; i--) {
            elementData[i + 1] = elementData[i];     // i=7    i+1=8  elementData[8]= elementData[7]
            // i=6    i+1=7  elementData[7]= elementData[6]
        }
        //将element元素存入index位置中
        elementData[index] = element;
        //添加完元素，size加1
        size++;
        return true;
    }

    /**
     * 确认当前数组容量是否满足的方法
     *
     * @param minCapacity
     */
    private void ensureCapacityInternal(int minCapacity) {
        //判断当前我们操作的数组是否有空间装下以上的元素
        if (minCapacity - elementData.length > 0) {
            //证明当前的数组的容量比我们需要的容量要小，需要进扩容
            //扩容的逻辑，直接设计一个新的方法去做,将我们需要最小的容量告诉方法，进行扩容创建
            this.grow(minCapacity);
        }


    }

    /**
     * 扩容的方法
     *
     * @param minCapacity 最小需要的容量大小
     */
    private void grow(int minCapacity) {
        //获取旧数组的容量
        int oldCapacity = elementData.length;
        //根据一个算法进行扩容，这里使用的是旧数组的容量1.5倍进行扩容，作为新数组的长度
        //扩容：原来的数组容量的1.5倍
        //(oldCapacity >> 1) :这里为什么不直接用oldCapacity*1.5呢？ 计算机底层是用二进制表示数字，直接进行右移动1位就是1.5的意思了
        // oldCapacity >> 1 :计算过程：
        //10的二进制表示：
        //0000000 0000000 0000000 0001010
        //右移两位：
        //10 >> 1 =4+1=5
        //00000000 0000000 0000000 000101
        //0000000 0000000 0000000 0000101
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //判断扩容之后，能否满足需要的最小的容量，如果计算出来的容量仍然不满足最小的容量，直接使用提供过来的最小容量
        if (newCapacity - oldCapacity <= 0) {
            newCapacity = oldCapacity;
        }
        //创建完新的数组后，将旧的元素复制新数组上，旧数组没有引用了，就会变成垃圾，垃圾回收会进行回收旧的数组
        //将旧数组的元素和新计算需要的容量大小传入,将创建新的数组进行接收，引用传递
        elementData = this.copyOf(elementData, newCapacity);

    }

    /**
     * 扩容复制容器元素的方法
     *
     * @param oldElementData 旧数组的元素
     * @param newCapacity    新的容量大小
     * @return 新的数组
     */
    private Object[] copyOf(Object[] oldElementData, int newCapacity) {
        //根据新的容量大小创建一个新的数组容器
        Object[] newElementData = new Object[newCapacity];
        //将旧数组的元素放入到新的数组
        for (int i = 0; i < elementData.length; i++) {
            newElementData[i] = elementData[i];
        }
        return newElementData;
    }


    /**
     * 查询的元素的方法
     * 从用户的角度，用户提供了一个下标后，返回一个数组的元素
     *
     * @param index
     * @return
     */
    public T get(int index) {
        //从当前的容器中，也就是当前的数组根据提供的下标获取元素
        //1、判断当前的下标是否符合规则，检查index的范围是否合法
        this.rangeCheck(index);
        //2、index的范围合法，根据数组的下标进行元素的获取
        return (T) elementData[index];
    }

    /**
     * 判断传入的数组下标位置是否合法
     *
     * @param index
     */
    private void rangeCheck(int index) {
        //判断当前传入的下标是否符合范围内
        if (index < 0 || index > size) {
            //如果当前的下标小于0或者大于当前的数组的存储的元素数量，直接抛出异常，告诉不合理的范围数组下标
            //自己定义一个异常(自己创建的类)来说明这个问题
            new BoxIndexOutOfBoundsException("Index:" + index + ",Size:" + size);
        }
    }


    public T remove(int index) {
        //判断传入的索引是否合理
        this.rangeCheck(index);
        //如果代码不抛出异常，说明传入的索引在合理的范围内
        //获取要删除元素的值,使用泛型
        T oldValue = (T) elementData[index];
        //从index开始 至size-1为止 将后面位置元素依次前移覆盖
        //index:要删除元素的索引的位置
        //size-1：实际元素个数最后一个元素索引位置，比如实际元素是8个，最后一个元素的索引下标就是size -1=7了
        for (int i = index; i < size - 1; i++) {
            //删除元素，其实就是对当前的索引位置往后的元素挨个往前挪，并不是真正的删除掉
            elementData[i] = elementData[i + 1];

        }
        //删除掉一个元素，最后size的索引位置的默认置空，并且计算容量减少size实际的元素-1
        //这里说明一下小知识点： elementData[size--] 这里可以拆分为两步的计算
        //size--：先自减，后赋值
        //1、size-- :先对size本身先自减1,得到的是数组最后一个元素的下标
        //2、elementData[size]=null，对组最后一个元素的置null
        elementData[size--] = null;
        //将删除掉的元素返回
        return oldValue;
    }

    /**
     * 由于我们的属性size是私有的，所以我们提供对爱暴露的方法给用户进行获取实际的因素
     *
     * @return 数组存入的个数
     */
    public int size() {
        //返回数组的实际存储的数量
        return this.size;
    }



    /**
     * 提供一个方法，提供给用户获取一个容器的容量大小
     *
     * @return 容器的容量大小
     */
    public int length() {
        //返回数组容量大小
        return elementData.length;
    }
}
